/**
 * Copyright (C) 2009 Matteo Merli <mm@merlimat.org>
 * 
 * $Id: JavaClass.java 21 2009-01-12 17:30:19Z matteo.merli $
 * $URL: http://pxl-lang.googlecode.com/svn/trunk/src/main/old_code/JavaClass.java $
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *         http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package pxl.types;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import pxl.errors.AttributeError;
import pxl.errors.PxlException;
import pxl.errors.TypeError;
import pxl.types.annotations.Exported;
import pxl.types.util.PxlArgument;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

public class JavaClass extends PxlObject
{

    private Class<Object> clazz;

    private List<Constructor<Object>> constructors = Lists.newArrayList();

    private Map<String, JavaMethod> staticMethods = Maps.newHashMap();
    private Map<String, JavaMethod> memberMethods = Maps.newHashMap();
    private Map<String, PxlObject> staticFields = Maps.newHashMap();
    private Map<String, PxlObject> memberFields = Maps.newHashMap();

    protected JavaClass( Class<Object> clazz )
    {
        this.clazz = clazz;

        // Read class constructors
        for ( Constructor<Object> constructor : clazz.getConstructors() )
            constructors.add( constructor );

        // Read class methods
        for ( Method method : clazz.getMethods() )
            if ( (method.getModifiers() & Modifier.STATIC) > 0 )
                staticMethods.put( method.getName(), new JavaMethod( method,
                        true ) );
            else
                memberMethods.put( method.getName(), new JavaMethod( method,
                        true ) );

        // Read class fields
        for ( Field field : clazz.getFields() )
        {

            PxlObject javaObject = null;
            try
            {
                javaObject = ConversionFactory.convertFromJava( field
                        .get( null ), this );

            } catch (Exception e)
            {
                System.err.println( "Exception: " + e );
                throw new RuntimeException( e );
            }

            if ( (field.getModifiers() & Modifier.STATIC) > 0 )
                staticFields.put( field.getName(), javaObject );
            else
                memberFields.put( field.getName(), javaObject );
        }
    }

    @Override
    public String getType()
    {
        return "JavaClass(" + getCompleteName() + ")";
    }

    public String getName()
    {
        return clazz.getSimpleName();
    }

    public String getCompleteName()
    {
        return clazz.getName();
    }

    public Class<Object> getInternalClass()
    {
        return clazz;
    }

    @Exported
    @Override
    public PxlObject __call__( List<PxlArgument> args )
    {
        // Get a new instance
        Object[] argValues;
        argValues = ConversionFactory.convertArgsToJava( args );
        Object res = null;

        for ( Constructor<Object> constructor : constructors )
        {

            try
            {
                res = constructor.newInstance( argValues );

            } catch (PxlException pe)
            {
                throw pe;
            } catch (InstantiationException ie)
            {
                // throw new TypeError( "Cannot instantiate an abstract class"
                // );
            } catch (IllegalAccessException iae)
            {
                // throw new TypeError( "Illegal access: " + iae );
            } catch (InvocationTargetException ite)
            {
                if ( ite.getCause() instanceof PxlException )
                    throw (PxlException) ite.getCause();

                // throw new TypeError( "Invocation target exception: " +
                // ite.getCause() );
            } catch (IllegalArgumentException e)
            {
                // throw new TypeError( "Illegal arguments: " + e );
            }
        }

        if ( res == null )
        {
            // res = None.getInstance();
            throw new TypeError( "Error instanciating class" );

        } else if ( !(res instanceof PxlObject) )
        {
            return ConversionFactory.convertFromJava( res );
        }

        // returned object is already a PxlObject
        return (PxlObject) res;
    }

    @Exported
    @Override
    public PxlObject __getattr__( String attrName )
    {
        /*
         * The only attributes a class can have are public static methods and
         * fields.
         */
        JavaMethod method = staticMethods.get( attrName );

        if ( method != null )
            return method;

        PxlObject field = staticFields.get( attrName );

        if ( field == null )
            throw new AttributeError( this, attrName );

        return field;
    }

    @Override
    public void __setattr__( String attrName, PxlObject value )
    {
        throw new TypeError( "Cannot set an attribute on a Java Class." );
    }

    public JavaMethod getStaticMethod( String methodName )
    {
        return staticMethods.get( methodName );
    }

    public JavaMethod getMemberMethod( String methodName )
    {
        return memberMethods.get( methodName );
    }

    public PxlObject getStaticField( String fieldName )
    {
        return staticFields.get( fieldName );
    }

    public PxlObject getMemberField( String fieldName )
    {
        return memberFields.get( fieldName );
    }

    public Collection<String> getMemberMethodNames()
    {
        return memberMethods.keySet();
    }

    public Collection<String> getMemberFieldNames()
    {
        return memberFields.keySet();
    }

    @Exported
    @Override
    public PxlList __dir__()
    {
        List<PxlString> list = Lists.newArrayList();
        for ( String method : staticMethods.keySet() )
            list.add( new PxlString( method ) );

        for ( String field : staticFields.keySet() )
            list.add( new PxlString( field ) );

        Collections.sort( list );
        return new PxlList( list );
    }

    // Attributes

    // / Not used
    @Override
    protected PxlObject getAttribute( String name )
    {
        return null;
    }

    @Override
    protected Collection<String> getAttributeNames()
    {
        return null;
    }

}
